//Copyright 2010 Balazs Keri, Imre Keri
//
//This file is part of FireworksCreator.
//
//FireworksCreator is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//FireworksCreator is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with FireworksCreator.  If not, see <http://www.gnu.org/licenses/>.
//
#include "controller.h"
#include "shooter.h"
#include "model/shooter.h"
#include <model/root.h>
#include <model/particletemplate.h>
#include <model/emittertemplate.h>
#include <osg/Geode>
#include <osgUtil/CullVisitor>
#include <algorithm>
//
namespace player
{
using namespace model;
//
Controller::Controller()
:
   osg::Node(),
   root(0),
   gravitation_enabled(false),
   friction_enabled(false),
   current_time(0),
   t0(-1),
   frame_number(0),
   is_frozen(true),
   wind()
{
   next_shooter=shooters.begin();
   setCullingActive(false);
}
Controller::Controller(const Controller &p_other,const osg::CopyOp &p_copyop)
:
   osg::Node(p_other,p_copyop),
   root(0),
   gravitation_enabled(p_other.gravitation_enabled),
   friction_enabled(p_other.friction_enabled),
   current_time(0),
   t0(-1),
   frame_number(0),
   is_frozen(true),
   wind(p_other.wind)
{
   next_shooter=shooters.begin();
   setCullingActive(false);
}
Controller::~Controller()
{
   qDebug()<<"Player::Controller::~Controller";
   for(ParticleDataMap::iterator pdi=particle_data.begin();pdi!=particle_data.end();++pdi)
   {
      delete pdi->second.factory;
      //delete pdi->second.system;
   }
   for(EmitterMap::iterator ei=emitters.begin();ei!=emitters.end();++ei)
   {
      delete ei->second;
   }
   for(ShooterVector::iterator si=shooters.begin();si!=shooters.end();++si)
   {
      delete *si;
   }
}
//
void Controller::assign_model(const model::Root &p_root)
{
   if(root)
      deassign_model();
   //
   root=&p_root;
   QSetIterator<ParticleTemplate*> pti(root->particle_templates);
   while(pti.hasNext())
   {
      ParticleTemplate *pt=pti.next();
      ParticleData &d=particle_data[pt];
      d.factory=new ParticleFactory();
      d.system=new ParticleSystem();
   }
   QSetIterator<EmitterTemplate*> eti(root->emitter_templates);
   while(eti.hasNext())
   {
      EmitterTemplate *et=eti.next();
      emitters[et]=new Emitter();
   }
   //
   for(ParticleDataMap::iterator pdi=particle_data.begin();pdi!=particle_data.end();++pdi)
   {
      ParticleData &pd=pdi->second;
      const ParticleTemplate *pt=pdi->first;
      pd.factory->init_setup(this,pd.system,pt);
      pd.system->assign_controller(this,pd.factory,pt);
   }
   for(EmitterMap::iterator ei=emitters.begin();ei!=emitters.end();++ei)
   {
      ei->second->init_setup(this,ei->first);
   }
   QSetIterator <model::Shooter*> si(root->shooters);
   while(si.hasNext())
   {
      model::Shooter *s=si.next();
      ShooterVector::iterator si1=shooters.begin();
      while(si1!=shooters.end()&&(*si1)->get_start_time()<=s->get_start_time())
         ++si1;
      (*(shooters.insert(si1,Shooter::create(s))))->init_setup(this,s);
   }
   next_shooter=shooters.begin();
}
void Controller::deassign_model()
{
   if(root)
   {
      root=0;
      for(ParticleDataMap::iterator pdi=particle_data.begin();pdi!=particle_data.end();++pdi)
      {
         delete pdi->second.factory;
      }
      particle_data.clear();
      for(EmitterMap::iterator ei=emitters.begin();ei!=emitters.end();++ei)
      {
         delete ei->second;
      }
      emitters.clear();
      for(ShooterVector::iterator si=shooters.begin();si!=shooters.end();++si)
      {
         delete *si;
      }
      shooters.clear();
      //
      current_shooters.clear();
      current_time=0;
      t0=-1;
      frame_number=0;
      is_frozen=true;
      next_shooter=shooters.begin();
   }
}
bool Controller::is_model_assigned()const
{
   return root!=0;
}
void Controller::play()
{
   if(root)
      is_frozen=false;
}
void Controller::pause()
{
   if(root)
      is_frozen=true;
}
void Controller::seek(double p_time)
{
   if(!root)
      return;
   //
   for(ParticleDataMap::iterator pdi=particle_data.begin();pdi!=particle_data.end();++pdi)
   {
      (*pdi).second.system->reset();
   }
   current_time=p_time;
   t0=-1;
   next_shooter=shooters.begin();
   while(next_shooter!=shooters.end()&&(*next_shooter)->get_start_time()<p_time)
      ++next_shooter;
   current_shooters.clear();
}
bool Controller::is_paused()const
{
   return is_frozen;
}
double Controller::get_current_time()const
{
   return current_time;
}
bool Controller::get_gravitation_enabled()const
{
   return gravitation_enabled;
}
void Controller::set_gravitation_enabled(bool p_gravitation_enabled)
{
   gravitation_enabled=p_gravitation_enabled;
   for(ParticleDataMap::iterator pdi=particle_data.begin();pdi!=particle_data.end();++pdi)
   {
      (*pdi).second.system->update_gravitation_enabled();
   }
}
bool Controller::get_friction_enabled()const
{
   return friction_enabled;
}
void Controller::set_friction_enabled(bool p_friction_enabled)
{
   friction_enabled=p_friction_enabled;
   for(ParticleDataMap::iterator pdi=particle_data.begin();pdi!=particle_data.end();++pdi)
   {
      (*pdi).second.system->update_friction_enabled();
   }
}
const osg::Vec3 &Controller::get_wind()const
{
   return wind;
}
void Controller::set_wind(const osg::Vec3 &p_wind)
{
   wind=p_wind;
}
void Controller::add_particle_systems(osg::Geode *p_geode)
{
   for(ParticleDataMap::iterator pdi=particle_data.begin();pdi!=particle_data.end();++pdi)
   {
      p_geode->addDrawable((*pdi).second.system.get());
   }
}
ParticleSystem *Controller::particle_system_for_template(const ParticleTemplate *p_particle_template)const
{
   ParticleDataMap::const_iterator pdi=particle_data.find(p_particle_template);
   if(pdi!=particle_data.end())
      return (*pdi).second.system;
   else
      return 0;
}
ParticleFactory *Controller::particle_factory_for_template(const ParticleTemplate *p_particle_template)const
{
   ParticleDataMap::const_iterator pdi=particle_data.find(p_particle_template);
   if(pdi!=particle_data.end())
      return (*pdi).second.factory;
   else
      return 0;
}
Emitter *Controller::emitter_for_template(const EmitterTemplate *p_emitter_template)const
{
   EmitterMap::const_iterator ei=emitters.find(p_emitter_template);
   if(ei!=emitters.end())
      return const_cast<Emitter*>(ei->second);
   else
      return 0;
}
//
void Controller::traverse(osg::NodeVisitor &nv)
{
   if(!root)
   {
      osg::Node::traverse(nv);
      return;
   }
   //
   osgUtil::CullVisitor *cv=dynamic_cast<osgUtil::CullVisitor*>(&nv);
   if(cv)
   {
      if(nv.getFrameStamp())
      {
         if(frame_number<nv.getFrameStamp()->getFrameNumber())
         {
            frame_number=nv.getFrameStamp()->getFrameNumber();
            double t=nv.getFrameStamp()->getSimulationTime();
            if(t0!=-1.0&&!is_frozen)
            {
               current_time+=t-t0;
               while(next_shooter!=shooters.end()&&(*next_shooter)->get_start_time()<=current_time)
               {
                  current_shooters.push_back(*next_shooter);
                  ++next_shooter;
               }
               ShooterList::iterator csi=current_shooters.begin();
               while(csi!=current_shooters.end())
               {
                  if((*csi)->update(t-t0))
                     ++csi;
                  else
                     csi=current_shooters.erase(csi);
               }
               for(ParticleDataMap::iterator i=particle_data.begin();i!=particle_data.end();++i)
               {
                  ParticleSystem *ps=(*i).second.system.get();
                  ParticleSystem::ScopedWriteLock lock(*(ps->get_read_write_mutex()));
                  if(ps->get_last_frame_number()>=(nv.getFrameStamp()->getFrameNumber()-1))
                  {
                     ps->update(t-t0);
                  }
               }
            }
            t0=t;
         }
      }
      else
      {
         osg::notify(osg::WARN)<<"player::Controller::traverse got no valid FrameStamp, particles not updated.\n";
      }
   }
   osg::Node::traverse(nv);
}
osg::BoundingSphere Controller::computeBound()const
{
   return osg::BoundingSphere();
}
}
